﻿/******************************************
 * 
 * @author Рамиль Алиякберов a.k.a R@Me0
 * http://www.rame0.ru
 * 
 * Data Provider для API ВКонтакте.
 * 
 * Реализует асинхронные запросы к функциям API из приложений на Silverlight
 * 
 * License:
 * Read License.txt
 */

using System;
using System.IO;
using System.Net;
using System.Xml;
using System.Xml.Linq;
using System.Windows.Threading;
using System.Collections.Generic;


namespace VKontakte
{
    /// <summary>
    /// Класс работы с данными на сервере
    /// </summary>
    public class DataProvider
    {
        public delegate void AsyncResultHandler(AsyncApiResponse callBack);

        /// <summary>
        /// Класс содержащий параметры для передачи в асинхронные вызовы
        /// </summary>
        public class AsyncApiResponse
        {
            public Dispatcher _dispatcher = null;
            public Delegate _callBack = null;
            public Delegate _errorCallBack = null;
            public Dictionary<string, string> _requestParameters = null;
            public HttpWebRequest _request = null;
            public XDocument _xmlResponse = null;
            public AsyncResultHandler _resultHandler = null;
            public string _errorMsg = null;
            public AsyncApiResponse(AsyncResultHandler resultHandler, Dispatcher dispatcher = null, Delegate callBack = null, Delegate errorCallBack = null)
            {
                _dispatcher = dispatcher;
                _callBack = callBack;
                _resultHandler = resultHandler;
                _request = null;
                _xmlResponse = null;
                _requestParameters = null;
                _errorMsg = null;
                _errorCallBack = errorCallBack;
            }
            public void SetGlobalCallback(Delegate errorCallBack = null)
            {
                _errorCallBack = errorCallBack;
            }
            public void SetHttpWebRequest(HttpWebRequest request, Dictionary<string, string> parameters)
            {
                _request = request;
                _requestParameters = parameters;
            }
            public void SetXmlResponse(XDocument xmlDocument)
            {
                _xmlResponse = xmlDocument;
            }
            public void SetErrorMsg(string msg)
            {
                _errorMsg = msg;
            }
        }

        private StartInfo startInfo;
        /// <summary>
        /// Конструктор
        /// </summary>
        /// <param name="StartInfo">Параметры запуска</param>
        public DataProvider(StartInfo info, Delegate globalErrorCallback=null)
        {
            startInfo = info;
            _globalErrorCallback = globalErrorCallback;
        }

        Delegate _globalErrorCallback = null;
        public Delegate GlobalErrorCallback
        {
            get { return _globalErrorCallback; }
        }

        #region Async
        public void Request(String method, AsyncApiResponse callBack, Dictionary<String, String> parameters = null)
        {
            try
            {
                if (_globalErrorCallback != null)
                    callBack.SetGlobalCallback(_globalErrorCallback);
                Dictionary<string, string> requestParameters = new Dictionary<string, string>();
                requestParameters.Add("api_id", startInfo.api_id);
                requestParameters.Add("method", method);
                requestParameters.Add("format", "XML");
                requestParameters.Add("v", "3.0");
                if (parameters != null)
                {
                    foreach (KeyValuePair<string, string> item in parameters)
                    {
                        requestParameters.Add(item.Key, item.Value);
                    }
                }
                if (startInfo.test_mode) requestParameters.Add("test_mode", "1");
                string signature = GenerateSignature(requestParameters);
                requestParameters.Add("sid", startInfo.sid);
                requestParameters.Add("sig", signature);
                

                callBack.SetHttpWebRequest((HttpWebRequest)WebRequest.Create(startInfo.api_url), requestParameters);
                callBack._request.UseDefaultCredentials = true;
                callBack._request.Method = "POST";
                callBack._request.ContentType = "application/x-www-form-urlencoded";
                //callBack._request.ContentType = "text/xml";

                
                callBack._request.BeginGetRequestStream(new AsyncCallback(GetRequestStreamCallback), callBack);
            }
            catch (Exception ex)
            {
                string error = ex.Message;
                callBack.SetXmlResponse(null);
                callBack.SetErrorMsg(error);
                callBack._resultHandler(callBack);
            }
            
        }

        private void GetRequestStreamCallback(IAsyncResult asynchronousResult)
        {
            AsyncApiResponse callBack = (AsyncApiResponse)asynchronousResult.AsyncState;
            try
            {
                System.IO.Stream postStream = callBack._request.EndGetRequestStream(asynchronousResult);
                String postData = "";

                Boolean isFirst = true;

                foreach (KeyValuePair<String, String> item in callBack._requestParameters)
                {
                    if (isFirst)
                    {
                        postData = item.Key + "=" + item.Value;
                        isFirst = false;
                    }
                    else
                        postData += "&" + item.Key + "=" + item.Value;
                }
                byte[] byteArray = System.Text.Encoding.UTF8.GetBytes(postData);

                postStream.Write(byteArray, 0, postData.Length);
                postStream.Close();

                callBack._request.BeginGetResponse(new AsyncCallback(GetResponseCallback), callBack);
            }
            catch (Exception ex)
            {
                string error = ex.Message;
                callBack.SetXmlResponse(null);
                callBack.SetErrorMsg(error);
                callBack._resultHandler(callBack);
            }
        }

        private void GetResponseCallback(IAsyncResult asynchronousResult)
        {
            AsyncApiResponse callback = (AsyncApiResponse)asynchronousResult.AsyncState;

            try
            {
                HttpWebResponse response = (HttpWebResponse)callback._request.EndGetResponse(asynchronousResult);
                if (response.StatusCode == HttpStatusCode.OK)
                {

                    Stream streamResponse = response.GetResponseStream();
                    XDocument xd = XDocument.Load(streamResponse);

                    callback.SetXmlResponse(xd);
                    callback._resultHandler(callback);
                }
                else
                {
                    callback.SetXmlResponse(null);
                    callback.SetErrorMsg("Error request");
                    callback._resultHandler(callback);
                }

            }
            catch (Exception ex)
            {
                string error = ex.Message;
                callback.SetXmlResponse(null);
                callback.SetErrorMsg(error);
                callback._resultHandler(callback);
            }
        }

        #endregion

        /// <summary>
        /// Генерация подписи для запроса
        /// </summary>
        /// <param name="requestParams">Параметры запроса</param>
        /// <returns>MD5 от конкатенации параметров запроса </returns>
        private String GenerateSignature(Dictionary<String, String> requestParams)
        {
            String signature = "";
            String[] sorted_array = new String[requestParams.Count];
            int i=0;
            foreach (KeyValuePair<String, String> pair in requestParams)
            {
                sorted_array[i] = (pair.Key + "=" + pair.Value);
                i++;
            }
            Array.Sort(sorted_array);
            

            // Note: Убедитесь, что параметр signature еще 
            //       не добавлен в массив request_params.
            foreach (String key in sorted_array) {
                signature += key;
            }
            if (Convert.ToInt32(startInfo.viewer_id) > 0) signature = startInfo.viewer_id.ToString() + signature;
            signature += startInfo.secret;

            return MD5Core.GetHashString(signature).ToLower();
        }
    }
}
